package com.atguigu.srb.core.service.impl;

import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.support.ExcelTypeEnum;
import com.atguigu.srb.base.asserts.Asserts;
import com.atguigu.srb.base.exception.BusinessException;
import com.atguigu.srb.base.response.ResponseEnum;
import com.atguigu.srb.core.listener.DictExcelDataListener;
import com.atguigu.srb.core.pojo.entity.Dict;
import com.atguigu.srb.core.mapper.DictMapper;
import com.atguigu.srb.core.pojo.entity.excel.DictExcelData;
import com.atguigu.srb.core.service.DictService;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.joda.time.DateTime;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
//import springfox.documentation.annotations.Cacheable;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * <p>
 * 数据字典 服务实现类
 * </p>
 *
 * @author Atguigu
 * @since 2023-05-26
 */
@Service
public class DictServiceImpl extends ServiceImpl<DictMapper, Dict> implements DictService {
//    @Resource
//    DictExcelDataListener dictExcelDataListener;
    @Resource
    RedisTemplate redisTemplate;
    @Resource
    DictService dictService;
    @Override
    public void importDicts(MultipartFile file) {
        //处理解析上传的excel文件的业务
        //1、判断上传的文件是否合法： 文件大小、文件扩展名
        Asserts.assertNotTrue(file.getSize()==0 , ResponseEnum.UPLOAD_ERROR);
        Asserts.assertNotTrue(!(file.getOriginalFilename().endsWith(".xls")||
                                file.getOriginalFilename().endsWith(".xlsx")||
                                file.getOriginalFilename().endsWith(".csv")),
                ResponseEnum.UPLOAD_ERROR);
        //2、为上传的文件创建javabean映射
        //3、创建监听器 处理解析excel数据
        //4、easyexcel配置监听器解析上传的excel文件
        try {
            EasyExcel.read(file.getInputStream())//基于上传的文件流解析
                    .head(DictExcelData.class)//表头映射的bean
                    .sheet(0)//工作簿索引
                    .registerReadListener(new DictExcelDataListener(baseMapper))//注册监听器
//                    .registerReadListener(dictExcelDataListener)//注册监听器
                    .doRead();//开始读取
        } catch (Exception e) {
            //catch到的异常对象才包含完整的异常的堆栈消息
            e.printStackTrace();
            throw new BusinessException(ResponseEnum.UPLOAD_ERROR);
        }
    }

    @Override
    public void exportDicts(HttpServletResponse response) {
        try {
            //查询数据字典集合
            List<Dict> dicts = this.list();
            //将数据字典集合转为 DictExcelData集合
            List<DictExcelData> dictExcelDatas = dicts.stream().map(dict -> {
                DictExcelData dictExcelData = new DictExcelData();
                //将dict对象的属性值设置给dictExcelData
                dictExcelData.setId(dict.getId());
                dictExcelData.setCode(dict.getDictCode());
                dictExcelData.setValue(dict.getValue());
                dictExcelData.setTitle(dict.getName());
                dictExcelData.setParentId(dict.getParentId());
                return dictExcelData;
            }).collect(Collectors.toList());
            //配置响应头：告诉浏览器响应体数据的格式(是一个附件)
            response.setHeader("content-disposition" , "attachment;filename=dicts_"
                        +new DateTime().toString("yyyy-MM-dd")
                    + ExcelTypeEnum.XLSX.getValue());
            //将数据字典集合写入到响应对象的响应体中
            EasyExcel.write(response.getOutputStream(),DictExcelData.class)
                    .sheet(0)
                    .doWrite(dictExcelDatas);
        } catch (Exception e) {
            throw new BusinessException(ResponseEnum.EXPORT_DATA_ERROR);
        }

    }
    //根据父id查询下一级数据字典集合
    @Cacheable(value = "srb:cache",key = "'dicts:'+#parentId")//value+key用来拼接缓存的key。 key属性值可以写#形参名 支持链式调用动态拼接key
    @Override
    public List<Dict> getDictsByParentId(String parentId) {
        //1、先从redis中查询缓存: 每个父id的下一级都是不一样的数据字典集合
        //redis的key使用:分隔便于图形化工具分组管理、key不能太大
//        String key = "srb:dicts:"+parentId;
//        Object result = redisTemplate.opsForValue().get(key);
//        //2、如果缓存存在返回缓存
//        if(result!=null){
//            return (List<Dict>) result;
//        }

        //3、如果缓存不存在 执行数据库查询  将结果存到redis中
        // select * from dict where parent_id = parentId
        List<Dict> dicts = this.list(Wrappers.lambdaQuery(Dict.class)
                .eq(Dict::getParentId, parentId));
        //每个Dict都需要设置它是否有下一级
        dicts.forEach(dict -> {
            //如果自己的id当做parentId查询下一级不为空 代表有下一级
            dict.setHasChildren(hasChildren(dict.getId()));

        });
        //redis中的数据基本上都会设置过期时间：
//        redisTemplate.opsForValue().set(key,dicts,600, TimeUnit.MINUTES);
        return dicts;
    }

    private boolean hasChildren(Long dictId) {
        boolean flag = this.count(Wrappers.lambdaQuery(Dict.class)
                .eq(Dict::getParentId , dictId))>0;
        return flag;
    }

    @Override
    public void deleteById(String id) {
        Dict dict = this.getById(id);
        //this类型是原来的DictServiceImpl对象，没有被动态代理
        //所以基于aop的动态代理都会失效
        //不能使用this调用，使用容器中获取的对象调用
        dictService.deleteByIdEvictCache(dict);
    }
    //    @CacheEvict(value = "srb:dicts",key = "#dict.dictCode")
    @CacheEvict(value = "srb:cache",key = "'dicts:'+#dict.parentId")
    public void deleteByIdEvictCache(Dict dict){
        //判断是否有子节点数据
        Asserts.assertNotTrue(hasChildren(dict.getId()),ResponseEnum.HAS_CHILDREN_ERROR);
        this.removeById(dict.getId());
    }
    //使用缓存管理
    @Cacheable(value = "srb:dicts" ,key = "#dictCode" )
    @Override
    public List<Dict> getDictsByDictCode(String dictCode) {
        return baseMapper.selectDictsByDictCode(dictCode);
    }

    @Override
    public String selectDictNameByDictCodeAndValue(String dictCode, Integer value) {
        //dictCode表示要查询的数据字典的 父记录的 dict_code的值
        //education中 value为4的
        // 1、先查询education下一级数据字典
        //2、 从下一级数据字典中挑选value值为4的
        String name = baseMapper.selectDictNameByDictCodeAndValue(dictCode,value);
        return name;
    }
}
